﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using HtmlAgilityPack;

namespace Jayden.Dll.Web.Crawler
{

	public abstract class DataProcessor
	{
		private UriValidator m_Validator = null;
		public abstract void ProcessData(WebCrawlerUri uri, string contentType, byte[] data, WebCrawler crawler);

		public UriValidator Validator { get { return m_Validator; } set { m_Validator = value; } }
	}

	public class HtmlDataProcessor : DataProcessor
	{
		override public void ProcessData(WebCrawlerUri uri, string contentType, byte[] data, WebCrawler crawler)
		{
			if (!contentType.StartsWith("text/html"))
				return;
			using (MemoryStream stream = new MemoryStream(data))
			{
				HtmlDocument htmlDocument = new HtmlDocument();
				htmlDocument.Load(stream);
				AddUris(uri, htmlDocument, "img", "src", crawler);
				AddUris(uri, htmlDocument, "a", "href", crawler);
				AddUris(uri, htmlDocument, "link", "href", crawler);
				AddUris(uri, htmlDocument, "form", "action", crawler);
				AddUris(uri, htmlDocument, "script", "src", crawler);
			}
		}

		public void AddUris(WebCrawlerUri uri, HtmlDocument htmlDocument, string tagName, string attributeName, WebCrawler crawler)
		{
			HtmlNodeCollection tags = htmlDocument.DocumentNode.SelectNodes("//" + tagName);
			if (tags == null)
				return;
			foreach (HtmlNode tag in tags)
			{
				HtmlAttribute attribute = tag.Attributes[attributeName];
				if (attribute != null && !string.IsNullOrWhiteSpace(attribute.Value))
				{
					Uri found = new Uri(uri.Uri, attribute.Value, true);
					WebCrawlerUri crawlerUri = new WebCrawlerUri(found, uri.Depth + 1);
					crawler.Project.AddUri(crawlerUri);
				}
			}
		}
	}
	public class WriteDataProcessor : DataProcessor
	{
		override public void ProcessData(WebCrawlerUri uri, string contentType, byte[] data, WebCrawler crawler)
		{
			string physicalPath = TransformToPath(uri.Uri, contentType, crawler.Project);
			if (!File.Exists(physicalPath))
			{
				string directory = Path.GetDirectoryName(physicalPath);
				Directory.CreateDirectory(directory);
				File.WriteAllBytes(physicalPath, data);
			}
		}

		public string TransformToPath(Uri uri, string contentType, WebCrawlerProject project)
		{
			string type = contentType.Split(';')[0];
			string[] typeParts = type.Split('/');
			string protocol = uri.Protocol;
			string domain = uri.Host;
			int port = uri.Port.HasValue ? uri.Port.Value : 80;
			string path = uri.AbsolutePath;
			string query = uri.GetQuery(true);
			query = query.Replace("?", "_query_");
			query = query.Replace("=", "_is_");
			query = query.Replace("&", "_and_");
			if (path.EndsWith("/"))
				path += "index.html";
			while (path.StartsWith("/"))
				path = path.Substring(1);
			path = path.Replace("/", "\\");
			string extension = Path.GetExtension(path);
			string extensionType = GetExtension(typeParts[1]);
			if (typeParts.Length == 2 && ("." + extensionType) != extension)
				extension = ("." + extensionType);
			string directory = Path.GetDirectoryName(path);
			string fileName = Path.GetFileNameWithoutExtension(path) + query + extension;
			string physicalPath = Path.Combine(project.Path, protocol, domain, port.ToString(), directory, fileName);
			return physicalPath;
		}

		public string GetExtension(string typePart)
		{
			if (typePart == "javascript")
				return "js";
			return typePart;
		}
	}
	public class HashDataProcessor : DataProcessor
	{

		private Dictionary<string, List<WebCrawlerUri>> m_UriByHash = new Dictionary<string, List<WebCrawlerUri>>();

		override public void ProcessData(WebCrawlerUri uri, string contentType, byte[] data, WebCrawler crawler)
		{
			SHA256Managed sha256 = new SHA256Managed();
			byte[] hash = sha256.ComputeHash(data);
			StringBuilder builder = new StringBuilder();
			foreach (byte b in hash)
				builder.Append(string.Format("{0:x2}", b));
			string key = builder.ToString();
			List<WebCrawlerUri> list = null;
			lock (m_UriByHash)
			{
				if (!m_UriByHash.TryGetValue(key, out list))
				{
					list = new List<WebCrawlerUri>();
					m_UriByHash.Add(key, list);
				}
			}
			lock (list)
			{
				list.Add(uri);
			}
		}
	}

}
